#pragma once

#include <ostream>
#include <istream>
#include <string>
#include <list>
#include <algorithm>
#include <unordered_map>
#include <unordered_set>
#include "tokenizer.h"

// Value type in parser
enum class ValueType : std::int32_t {
    NONE = 0,
    STRUCT,
    STRING,
    VOID,
    I8,
    I16,
    I32,
    I64,
    UI8,
    UI16,
    UI32,
    UI64,
    FLOAT,
    DOUBLE,
    BOOL,
    SET,
    SEQ,
    DICT,
};

// Common node for struct's field, method return type and method argment
struct IdlNode {
    IdlNode(const IdlNode&) = delete;
    const IdlNode& operator=(const IdlNode&) = delete;
    // ctor
    IdlNode() {
        type = ValueType::NONE;
        first = nullptr;
        second = nullptr;
    }
    // rvalue ctor
    IdlNode(IdlNode&& rht) noexcept {
        type = rht.type;
        token = rht.token;
        first = rht.first;
        second = rht.second;
        rht.first = nullptr;
        rht.second = nullptr;
    }
    // dtor
    ~IdlNode() {
        type = ValueType::NONE;
        if (first) {
            delete first;
        }
        if (second) {
            delete second;
        }
        first = nullptr;
        second = nullptr;
    }
    // rvalue assignment
    const IdlNode& operator=(IdlNode&& rht) noexcept {
        type = rht.type;
        token = rht.token;
        first = rht.first;
        second = rht.second;
        rht.first = nullptr;
        rht.second = nullptr;
        return *this;
    }
    ValueType type {ValueType::NONE}; ///< Value type
    Token token; ///< The associate token
    IdlNode* first {nullptr}; ///< extenstion
    IdlNode* second {nullptr}; ///< extension
    ///< Validation
    operator bool() {
        return (type != ValueType::NONE);
    }
};

// Service Method
struct IdlMethod {
    IdlMethod(const IdlMethod&) = delete;
    const IdlMethod& operator=(const IdlMethod&) = delete;
    // ctor
    IdlMethod() {}
    // rvalue ctor
    IdlMethod(IdlMethod&& rht) noexcept {
        name = std::move(rht.name);
        retType = std::move(rht.retType);
        oneway = rht.oneway;
        timeout = rht.timeout;
        retry = rht.retry;
        no_except = rht.no_except;
        arg_list = std::move(rht.arg_list);
    }
    // rvalue assignment
    IdlMethod& operator=(IdlMethod&& rht) noexcept {
        name = std::move(rht.name);
        retType = std::move(rht.retType);
        oneway = rht.oneway;
        timeout = rht.timeout;
        retry = rht.retry;
        no_except = rht.no_except;
        arg_list = std::move(rht.arg_list);
        return *this;
    }
    std::string name; ///< method name
    IdlNode retType; ///< The return type of method
    int timeout {5000}; // Method timeout
    int retry {0}; // Max retry times
    static const int DEFAULT_MAX_RETRY = 3;
    std::list<IdlNode> arg_list; ///< Arguments
    bool oneway {false}; ///< One way method, just call and return immediatelly and only support return type of void
    bool no_except{ false }; ///< never throw exception
    // Validation
    operator bool() {
        return (!name.empty());
    }
};

// Struct
struct IdlStruct {
    IdlStruct(const IdlStruct&) = delete;
    const IdlStruct& operator=(const IdlStruct&) = delete;
    // ctor
    IdlStruct() {}
    // rvalue ctor
    IdlStruct(IdlStruct&& rht) noexcept {
        name = std::move(rht.name);
        field_list = std::move(rht.field_list);
    }
    // rvalue assignment
    IdlStruct& operator=(IdlStruct&& rht) noexcept {
        name = std::move(rht.name);
        field_list = std::move(rht.field_list);
        return *this;
    }
    std::string name; ///< struct name
    std::list<IdlNode> field_list; /// struct fields
    // Validation
    operator bool() {
        return (!name.empty());
    }
    // Add a struct field
    bool addField(IdlNode&& field) {
        for (auto& node : field_list) {
            if (node.token.getText() == field.token.getText()) {
                return false;
            }
        }
        field_list.emplace_back(std::move(field));
        return true;
    }
};

enum class ServiceType {
    NONE = 0,
    MULTIPLE = 1,
    SINGLE,
    REENTRANT,
    GENERIC,
};

enum class ServiceLoadType {
    NONE = 0,
    STATIC = 1,
    DYNAMIC,
};

struct IdlServiceNotation {
    std::string key;
    std::vector<std::string> values;
};

// Service
struct IdlService {
    IdlService(const IdlService&) = delete;
    const IdlService& operator=(const IdlService&) = delete;
    // ctor
    IdlService() {}
    // rvalue ctor
    IdlService(IdlService&& rht) noexcept {
        name = std::move(rht.name);
        type = rht.type;
        fileName = std::move(rht.fileName);
        uuid = std::move(rht.uuid);
        method_list = std::move(rht.method_list);
        maxInst = rht.maxInst;
        loadType = rht.loadType;
        service_notations = std::move(rht.service_notations);
    }
    // rvalue assignment
    IdlService& operator=(IdlService&& rht) noexcept {
        name = std::move(rht.name);
        type = rht.type;
        fileName = std::move(rht.fileName);
        uuid = std::move(rht.uuid);
        method_list = std::move(rht.method_list);
        maxInst = rht.maxInst;
        loadType = rht.loadType;
        service_notations = std::move(rht.service_notations);
        return *this;
    }
    std::string name; // Service name
    ServiceType type { ServiceType::NONE }; // Service type
    std::string fileName; // The file which service belong to
    std::string uuid; // Service UUID
    std::size_t maxInst{0}; // Maximum service instance count
    std::list<IdlMethod> method_list; ///< service methods
    using ServiceNotations = std::unordered_map<std::string, IdlServiceNotation>;
    ServiceNotations service_notations; ///< service notation
    ServiceLoadType loadType {ServiceLoadType::STATIC}; ///< service load type
    // Validation
    operator bool() {
        return (!name.empty());
    }
    // Add service method
    bool addMethod(IdlMethod&& method) {
        for (auto& other : method_list) {
            if (other.name == method.name) {
                return false;
            }
        }
        method_list.emplace_back(std::move(method));
        return true;
    }
};

using FileSet = std::unordered_set<std::string>;
using StructSet = std::unordered_set<std::string>;
using ServiceMap = std::unordered_map<std::string, IdlService>;
using StructList = std::list<IdlStruct>;

class Frontend;

class Parser {
    FileSet fileSet_; ///< All .idl file
    StructSet structSet_; ///< All struct name
    StructList structList_; ///< All struct
    ServiceMap serviceMap_; ///< All service
    std::string error_; ///< error string
    std::string curFile_; ///< Current .idl file

    using TokenizerList = std::list<Tokenizer*>;
    TokenizerList tokenizerList_; ///< All tokenizer
    Tokenizer* curTokenizer_ {nullptr}; // Current tokenizer
    bool validationFailed_ {false}; // Fail to validating
    Frontend* frontend_ {nullptr};

public:
    ///< exe 
    static std::string workingPath_;
    ///< include file search root
    static std::vector<std::string> includePathlist_;
public:
    Parser(Frontend* frontend) : frontend_(frontend) {}
    // dtor
    ~Parser();
    // Analyze file
    // @param file file name
    // @param os output stream for error
    // @param file search path 
    // @retval true success
    // @retval false failure
    bool analyze(const std::string& file, std::ostream& os, const std::string& searchpath);
    // Validation
    // @param os output stream for error
    // @retval true success
    // @retval false failure
    bool validate(std::ostream& os);
    // Returns all IDL file
    const FileSet& getFiles() const { return fileSet_; }
    // Returns all struct
    const StructSet& getStructs() const { return structSet_; }
    // Returns all struct
    const StructList& getStructList() const { return structList_; }
    // Returns all service
    const ServiceMap& getService() const { return serviceMap_; }
    // Return error
    const std::string& getError() const { return error_; }

private:
    // Validates all struct
    // @param os output stream
    bool validateAllStruct(std::ostream& os);
    // Validates all service
    // @param os output stream
    bool validateAllService(std::ostream& os);
    // Validates all field in struct
    // @param os output stream
    // @param field struct field
    bool validateStructField(std::ostream& os, IdlNode& field, std::list<std::string>& ref);
    bool validateServiceMethod(std::ostream& os, IdlMethod& method);
    bool validateMethodArgument(std::ostream& os, IdlNode& arg);
    // Start to parse an IDL file
    // @param file file name
    // @param tokenizer Tokenizer
    // @param os error output stream
    // @param file search path
    // @retval true success
    // @retval false failure
    bool parse(const std::string& file, Tokenizer& tokenizer, std::ostream& os, const std::string& searchpath);
    // Start to parse an IDL chunk
    // @param is std::istream
    // @param tokenizer Tokenizer
    // @param os error output stream
    // @param raiseError throw exception or not
    // @retval true success
    // @retval false failure
    bool doChunk(std::istream& is, Tokenizer& tokenizer, std::ostream& os, bool raiseError);
    // Start to parse an IDL struct
    // @param is std::istream
    // @param tokenizer Tokenizer
    // @param os error output stream
    void doStruct(std::istream& is, Tokenizer& tokenizer, std::ostream& os);
    // Start to parse an IDL chunk in struct or service
    // @param is std::istream
    // @param tokenizer Tokenizer
    // @param os error output stream
    // @param cur current token
    void doWrapChunk(std::istream& is, Tokenizer& tokenizer, std::ostream& os, Token& cur);
    // Start to parse field in a struct
    // @param is std::istream
    // @param tokenizer Tokenizer
    // @param cur current token
    // @return a IDL node
    IdlNode doStructField(std::istream& is, Tokenizer& tokenizer, Token& cur);
    // Start to parse an IDL service
    // @param is std::istream
    // @param tokenizer Tokenizer
    // @param os error output stream
    void doService(std::istream& is, Tokenizer& tokenizer, std::ostream& os);
    // Start to parse an IDL service notation
    // @param is std::istream
    // @param tokenizer Tokenizer
    // @param os error output stream
    IdlServiceNotation doNotation(std::istream& is, Tokenizer& tokenizer, Token& cur);
    // Start to parse method in a service
    // @param is std::istream
    // @param tokenizer Tokenizer
    // @param cur current token
    // @return a IDL service method
    IdlMethod doMethod(std::istream& is, Tokenizer& tokenizer, Token& cur);
    // Start to parse return type in a method
    // @param is std::istream
    // @param tokenizer Tokenizer
    // @param cur current token
    // @return a IDL node
    IdlNode doMethodRet(std::istream& is, Tokenizer& tokenizer, Token& cur);
    // Start to parse argument in a method
    // @param is std::istream
    // @param tokenizer Tokenizer
    // @param cur current token
    // @return a IDL node
    IdlNode doMethodArg(std::istream& is, Tokenizer& tokenizer, Token& cur);
    // Start to parse single line comment
    // @param is std::istream
    // @param tokenizer Tokenizer
    void doSingleComment(std::istream& is, Tokenizer& tokenizer);
    // Start to parse multi-line comment
    // @param is std::istream
    // @param tokenizer Tokenizer
    void doMultiComment(std::istream& is, Tokenizer& tokenizer);
    // Start to parse an IDL import file
    // @param is std::istream
    // @param tokenizer Tokenizer
    // @param os error output stream
    void doImport(std::istream& is, Tokenizer& tokenizer, std::ostream& os);
    // Skip all token until a non-blank and non-comment token
    // @param is std::istream
    // @param tokenizer Tokenizer
    // @return current token
    Token& skipBlankNoEof(std::istream& is, Tokenizer& tokenizer);
    // Add a struct
    // @param stru struct instance
    void addStruct(IdlStruct& stru);
    // Start to parse a identifier field in struct or argument in service
    // @param is std::istream
    // @param tokenizer Tokenizer
    // @param field IDL node
    // @param withName has a name?
    void doFieldIdentifier(std::istream& is, Tokenizer& tokenizer, IdlNode& field, bool withName);
    // Start to parse a sequnce field in struct or argument in service
    // @param is std::istream
    // @param tokenizer Tokenizer
    // @param field IDL node
    // @param withName has a name?
    void doFieldSeq(std::istream& is, Tokenizer& tokenizer, IdlNode& field, bool withName);
    // Start to parse a set field in struct or argument in service
    // @param is std::istream
    // @param tokenizer Tokenizer
    // @param field IDL node
    // @param withName has a name?
    void doFieldSet(std::istream& is, Tokenizer& tokenizer, IdlNode& field, bool withName);
    // Start to parse a dictionary field in struct or argument in service
    // @param is std::istream
    // @param tokenizer Tokenizer
    // @param field IDL node
    // @param withName has a name?
    void doFieldDict(std::istream& is, Tokenizer& tokenizer, IdlNode& field, bool withName);
    // Generates token error string
    // @param token token
    // @param os output stream
    // @return error string
    std::string getTokenError(Token& token, std::ostream& os);

    void doPreKeyword(IdlMethod& method, std::istream& is, Tokenizer& tokenizer, Token& cur);
};
